﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.NetworkInformation;
using System.Reflection;
using System.Threading;
using System.Windows.Forms;

namespace Ellanet
{
    public partial class PingForm : Form
    {
        private const string HomeAddress = "http://pingythingy.codeplex.com/";
        private const bool ContinuallyPing = true;

        private System.Timers.Timer _pingTimer;
        private int _pingInterval = 10000;
        private bool _pingSuccessfulHosts = true;

        private delegate string GetButtonTextDelegate(ref Button b);

        private delegate void SetButtonTextDelegate(ref Button b, string text);

        private delegate ListViewItem GetListViewItemAtIndexDelegate(ref ListView lv, int index);

        private delegate void AddListViewSubItemsDelegate(ref ListView lv, int index, string[] items);

        private delegate void SetListViewItemTagDelegate(ref ListView lv, int index, object tag);

        private delegate void ButtonPerformClickDelegate(ref Button b);

        public PingForm()
        {
            InitializeComponent();
            Load += PingForm_Load;
            FormClosing += PingForm_FormClosing;
            addButton.Click += addButton_Click;
            selectAllButton.Click += selectAllButton_Click;
            removeButton.Click += removeButton_Click;
            resetButton.Click += resetButton_Click;
            exportButton.Click += exportButton_Click;
            pingButton.Click += pingButton_Click;
            optionsButton.Click += optionsButton_Click;
            hostListView.ItemSelectionChanged += hostListView_ItemSelectionChanged;
        }

        private void PingForm_Load(object sender, EventArgs e)
        {
            Text = String.Format("PingyThingy ({0}.{1}.{2}) - {3}", Assembly.GetExecutingAssembly().GetName().Version.Major, Assembly.GetExecutingAssembly().GetName().Version.Minor, Assembly.GetExecutingAssembly().GetName().Version.Build, HomeAddress);
        }

        private void optionsButton_Click(object sender, EventArgs e)
        {
            var options = new Options(_pingInterval/1000, _pingSuccessfulHosts, ContinuallyPing);

            if (options.ShowDialog() == DialogResult.OK)
            {
                _pingInterval = options.PingInterval*1000;
                _pingSuccessfulHosts = options.PingSuccessfulHosts;
                //continuallyPing = options.ContinuallyPing;

                if (_pingTimer != null)
                {
                    _pingTimer.Interval = _pingInterval;
                }
            }
        }

        private void PingForm_FormClosing(object sender, FormClosingEventArgs e)
        {
            if ((_pingTimer != null) && _pingTimer.Enabled)
            {
                _pingTimer.Stop();
            }
        }

        private void DetermineButtonState()
        {
            bool enabled = false;

            if (hostListView.Items.Count > 0)
            {
                enabled = true;
            }
            else
            {
                if ((_pingTimer != null) && _pingTimer.Enabled)
                {
                    pingButton.PerformClick();
                }
            }

            selectAllButton.Enabled = enabled;
            pingButton.Enabled = enabled;
            resetButton.Enabled = enabled;
            exportButton.Enabled = enabled;
        }

        private void hostListView_ItemSelectionChanged(object sender, ListViewItemSelectionChangedEventArgs e)
        {
            removeButton.Enabled = hostListView.SelectedItems.Count > 0;
        }

        private void pingButton_Click(object sender, EventArgs e)
        {
            if (GetButtonText(ref pingButton).Equals("Stop"))
            {
                SetButtonText(ref pingButton, "Start Ping");
                _pingTimer.Stop();
            }
            else
            {
                resetButton.PerformClick();
                SetButtonText(ref pingButton, "Stop");
                _pingTimer = new System.Timers.Timer();
                _pingTimer.Elapsed += pingTimer_Elapsed;
                _pingTimer.Interval = 500;
                _pingTimer.Start();
            }
        }

        private void pingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            int sent = 0;
            _pingTimer.Interval = _pingInterval;
            var consecutiveInts = Enumerable.Range(0, hostListView.Items.Count).ToList();

            foreach (int i in consecutiveInts.OrderBy(a => Guid.NewGuid()))
            {
                var lvi = GetListViewItemAtIndex(ref hostListView, i);

                if (_pingSuccessfulHosts || !HostIsSuccessful(i))
                {
                    sent++;

                    if ((lvi.Tag == null) || (lvi.Tag.Equals(0)))
                    {

                        SetListViewItemTag(ref hostListView, i, 1);
                        ThreadPool.QueueUserWorkItem(PingHost, i);
                    }
                }
            }

            Debug.WriteLine(String.Format("Sent = {0}", sent));

            if (!_pingSuccessfulHosts && sent.Equals(0))
            {
                ButtonPerformClick(ref pingButton);
            }

            //while (!continuallyPing && (GetAvailableThreads() < (startAvailableThreads - 1)))
            //{
            //    Debug.WriteLine(GetAvailableThreads());
            //    Thread.Sleep(100);
            //}

            //if (!continuallyPing)
            //{
            //    pingButton.PerformClick();
            //}
        }

        private bool HostIsSuccessful(int index)
        {
            try
            {
                return GetListViewItemAtIndex(ref hostListView, index).SubItems[1].Text.Equals("Success", StringComparison.CurrentCultureIgnoreCase);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }

            return false;
        }

        //private static int GetAvailableThreads()
        //{
        //    int workerThreads;
        //    int completionPortThreads;
        //    ThreadPool.GetAvailableThreads(out workerThreads, out completionPortThreads);
        //    return workerThreads;
        //}

        private void PingHost(object state)
        {
            try
            {
                if ((_pingTimer != null) && _pingTimer.Enabled)
                {
                    var pingSender = new Ping();
                    PingReply reply = null;
                    string host = GetListViewItemAtIndex(ref hostListView, (int) state).Text;

                    try
                    {
                        reply = pingSender.Send(host);
                    }
                    catch (Exception ex)
                    {
                        Debug.WriteLine(ex.Message);
                    }

                    var subItems = new List<string>();

                    if (reply == null)
                    {
                        subItems.Add("UnknownHost");
                    }
                    else
                    {
                        subItems.Add(reply.Status.ToString());
                        subItems.Add(GetHostIPAddress(host));
                        subItems.Add(reply.RoundtripTime.ToString(CultureInfo.InvariantCulture));
                    }

                    AddListViewSubItems(ref hostListView, (int) state, subItems.ToArray());
                }
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }

            try
            {
                SetListViewItemTag(ref hostListView, (int) state, 0);
            }
            catch (Exception ex)
            {
                Debug.WriteLine(ex.Message);
            }
        }

        private void exportButton_Click(object sender, EventArgs e)
        {
            var sfd = new SaveFileDialog
                          {
                              CheckFileExists = false,
                              CheckPathExists = true,
                              DefaultExt = "csv",
                              Filter = "Comma Separated Values (*.csv)|*.csv",
                              OverwritePrompt = true,
                              Title = "PingyThingy Export",
                              ValidateNames = true
                          };

            if (sfd.ShowDialog() == DialogResult.OK)
            {
                try
                {
                    var sw = new StreamWriter(sfd.FileName);
                    sw.WriteLine("Host,Status,Address,Latency (ms)");

                    for (int i = 0; i < hostListView.Items.Count; i++)
                    {
                        var lvi = GetListViewItemAtIndex(ref hostListView, i);
                        string line = null;

                        foreach (ListViewItem.ListViewSubItem lvsi in lvi.SubItems)
                        {
                            line += String.Format("{0},", lvsi.Text);
                        }

                        sw.WriteLine(line);
                    }

                    sw.Close();
                }
                catch (Exception ex)
                {
                    Debug.WriteLine(ex.Message);
                }
            }
        }

        private void resetButton_Click(object sender, EventArgs e)
        {
            for (int i = 0; i < hostListView.Items.Count; i++)
            {
                AddListViewSubItems(ref hostListView, i, new string[] {});
            }
        }

        private void removeButton_Click(object sender, EventArgs e)
        {
            foreach (ListViewItem lvi in hostListView.SelectedItems)
            {
                hostListView.Items.Remove(lvi);
            }

            DetermineButtonState();
        }

        private void selectAllButton_Click(object sender, EventArgs e)
        {
            bool selected = !(hostListView.SelectedItems.Count > 0);

            for (int i = 0; i < hostListView.Items.Count; i++)
            {
                hostListView.Items[i].Selected = selected;
            }

            if (selected)
            {
                hostListView.Focus();
            }
        }

        private void addButton_Click(object sender, EventArgs e)
        {
            var ah = new AddHosts();

            if (ah.ShowDialog() == DialogResult.OK)
            {
                foreach (string host in ah.Hosts)
                {
                    if (!HostInListView(host))
                    {
                        hostListView.Items.Add(host);
                    }
                }
            }

            DetermineButtonState();
        }

        private bool HostInListView(string host)
        {
            if (hostListView.Items.Count > 0)
            {
                foreach (ListViewItem lvi in hostListView.Items)
                {
                    if (!String.IsNullOrEmpty(lvi.Text) && lvi.Text.Equals(host, StringComparison.CurrentCultureIgnoreCase))
                    {
                        return true;
                    }
                }
            }

            return false;
        }

        private string GetButtonText(ref Button b)
        {
            if (InvokeRequired)
            {
                return Invoke(new GetButtonTextDelegate(GetButtonText), new object[] {b}).ToString();
            }

            return b.Text;
        }

        private void SetButtonText(ref Button b, string text)
        {
            if (InvokeRequired)
            {
                Invoke(new SetButtonTextDelegate(SetButtonText), new object[] {b, text});
            }
            else
            {
                b.Text = text;
            }
        }

        private ListViewItem GetListViewItemAtIndex(ref ListView lv, int index)
        {
            if (InvokeRequired)
            {
                return (ListViewItem) Invoke(new GetListViewItemAtIndexDelegate(GetListViewItemAtIndex), new object[] {lv, index});
            }

            if ((lv != null) && (lv.Items.Count > 0) && (lv.Items.Count > index))
            {
                return lv.Items[index];
            }

            return null;
        }

        private void AddListViewSubItems(ref ListView lv, int index, string[] items)
        {
            if (InvokeRequired)
            {
                Invoke(new AddListViewSubItemsDelegate(AddListViewSubItems), new object[] {lv, index, items});
            }
            else
            {
                if ((lv != null) && (lv.Items.Count > 0) && (lv.Items.Count > index))
                {
                    string host = lv.Items[index].Text;
                    lv.Items[index].SubItems.Clear();
                    lv.Items[index].Text = host;

                    foreach (string item in items)
                    {
                        lv.Items[index].SubItems.Add(item);
                    }
                }
            }
        }

        private void SetListViewItemTag(ref ListView lv, int index, object tag)
        {
            if (InvokeRequired)
            {
                Invoke(new SetListViewItemTagDelegate(SetListViewItemTag), new[] {lv, index, tag});
            }
            else
            {
                if ((lv != null) && (lv.Items.Count > 0) && (lv.Items.Count > index))
                {
                    lv.Items[index].Tag = tag;
                }
            }
        }

        private void ButtonPerformClick(ref Button b)
        {
            if (InvokeRequired)
            {
                Invoke(new ButtonPerformClickDelegate(ButtonPerformClick), new object[] {b});
            }
            else
            {
                b.PerformClick();
            }
        }

        private string GetHostIPAddress(string host)
        {
            IPHostEntry hostEntry = Dns.GetHostEntry(host);

            if ((hostEntry.AddressList != null) && (hostEntry.AddressList.Length > 0))
            {
                return hostEntry.AddressList[0].ToString();
            }

            return null;
        }
    }
}
